/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini

   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

   In this example rxValue is the data received (only accessible inside that function).
   And txValue is the data to be sent, in this example just a byte incremented every second.
*/
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#ifndef GIT_REV
#define GIT_REV "unknown"
#warning "GIT_REV not defined, using default"
#endif

#ifndef BUILD_TIME
#define BUILD_TIME "unknown"
#warning "BUILD_TIME not defined, using default"
#endif

#define VERSION "0.1.1_" GIT_REV "_" BUILD_TIME
#define LED_PIN 2

// https://lastminuteengineers.com/esp32-pinout-reference/
// U2_RXD	GPIO16	U2_CTS	GPIO8
// U2_TXD	GPIO17	U2_RTS	GPIO7

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
static volatile uint16_t cur_mtu = 20;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

class MyServerCallbacks : public BLEServerCallbacks
{
    void onConnect(BLEServer *pServer)
    {
        uint16_t id = pServer->getConnId();
        uint16_t mtu = pServer->getPeerMTU(id);
        cur_mtu = mtu - 3;
        Serial.printf("onConnect id:%d mtu:%d\n", id, mtu);
        deviceConnected = true;
    };

    void onDisconnect(BLEServer *pServer)
    {
        Serial.printf("onDisconnect\n");
        deviceConnected = false;
    }
    void onMtuChanged(BLEServer *pServer, esp_ble_gatts_cb_param_t *param)
    {
        cur_mtu = param->mtu.mtu - 3;
        Serial.printf("onMtuChanged: %d\n", cur_mtu);
    }
};

class MyCallbacks : public BLECharacteristicCallbacks
{
    void onWrite(BLECharacteristic *pCharacteristic)
    {
        std::string rxValue = pCharacteristic->getValue();
        
        if (!rxValue.empty())
        {
            Serial.println("*********");
            Serial.print("Received Value: ");
            Serial.println(rxValue.c_str());
            Serial.println("*********");

            if (rxValue == "ble_version") {
                std::string versionInfo = VERSION;
                pTxCharacteristic->setValue((uint8_t*)versionInfo.c_str(), versionInfo.length());
                pTxCharacteristic->notify();
                return;
            }

            Serial2.write(rxValue.c_str(), rxValue.length());
        }
    }
    void onNotify(BLECharacteristic *pCharacteristic)
    {
        Serial.printf("onNotify:%s\n", pCharacteristic->getUUID().toString().c_str());
    }
};

class MyCallbacks2 : public BLEDescriptorCallbacks
{
    void onRead(BLEDescriptor *pDescriptor)
    {
        Serial.printf("Desc onRead:%s\n", pDescriptor->getUUID().toString().c_str());
    }
    void onWrite(BLEDescriptor *pDescriptor)
    {
        Serial.printf("Desc onWrite:%s\n", pDescriptor->getUUID().toString().c_str());
    }
};

void setup()
{
    Serial.begin(115200);
    Serial2.begin(115200);

    // Create the BLE Device
    BLEDevice::init("UART Service");

    // Create the BLE Server
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks());

    // Create the BLE Service
    BLEService *pService = pServer->createService(SERVICE_UUID);

    // Create a BLE Characteristic
    pTxCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_TX,
        BLECharacteristic::PROPERTY_NOTIFY);

    BLEDescriptor *pDescriptor = new BLE2902();
    pDescriptor->setCallbacks(new MyCallbacks2());
    pTxCharacteristic->addDescriptor(pDescriptor);
    pTxCharacteristic->setCallbacks(new MyCallbacks());

    BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
        CHARACTERISTIC_UUID_RX,
        BLECharacteristic::PROPERTY_WRITE);

    pRxCharacteristic->setCallbacks(new MyCallbacks());

    // Start the service
    pService->start();

    // Start advertising
    pServer->getAdvertising()->start();
    Serial.printf("Build Time    : %s %s\n", __DATE__, __TIME__);
    Serial.printf("Build Version : %s\n", VERSION);
    Serial.printf("Ble Mac       : %s\n", BLEDevice::getAddress().toString().c_str());
    Serial.println("Waiting a client connection to notify...");
    Serial.println("Set pin23 output to power on 4G");
    pinMode(23, OUTPUT);
    Serial.println("digitalWrite pin23 0");
    digitalWrite(23, 0);
    delay(1000);
    Serial.println("digitalWrite pin23 1");
    digitalWrite(23, 1);
    pinMode(LED_PIN, OUTPUT);
}

static uint16_t num = 0;
static uint16_t led_time = 500;
static uint16_t led_time_total = 2000;
void loop()
{
    int readn = 0;
    int writen = 0;
    int curn = 0;
    uint8_t buffer[ESP_GATT_MAX_ATTR_LEN] = {0};
    if ((readn = Serial2.available()) > 0)
    {
        Serial.printf("Serial2.available %d\n", readn);
        if (deviceConnected)
        {
            do
            {
                if (readn > cur_mtu)
                {
                    curn = cur_mtu;
                }
                else
                {
                    curn = readn;
                }
                readn -= curn;
                writen = Serial2.readBytes(buffer, curn);
                if (writen != curn)
                {
                    Serial.printf("Serial1 read %d != %d\n", writen, curn);
                }
                pTxCharacteristic->setValue(buffer, writen);
                pTxCharacteristic->notify();
                buffer[writen] = '\0';
                Serial.printf("read ser:[%s]\n", buffer);
            } while (readn > 0);
        }
        else
        {
            Serial2.readBytes(buffer, readn);
            Serial.printf("Device not connected!\n");
        }
    }

    // disconnecting
    if (!deviceConnected && oldDeviceConnected)
    {
        auto info = "{\"method\": \"ble\",\"status\": \"disconnect\",\"version\": \"" VERSION "\"}\r";
        Serial2.write(info, strlen(info));
        delay(500);                  // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
        num = 0;
        led_time = 500;
        led_time_total = 2000;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected)
    {
        auto info = "{\"method\": \"ble\",\"status\": \"connected\",\"version\": \"" VERSION "\"}\r";
        Serial2.write(info, strlen(info));
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
        num = 0;
        led_time = 200;
        led_time_total = 400;
    }
    delay(1);
    if (0 == num)
    {
        digitalWrite(LED_PIN, 1);
    }
    if (led_time == num)
    {
        digitalWrite(LED_PIN, 0);
    }
    num++;
    if (num > led_time_total)
        num = 0;
}
